REBOL [
	Title: "disreb"
	License: "GPLv3"
	Date: 2011-02-27
	Purpose: "API for REDIS no-SQL db."
	Version: 2.2.1 ; Matches REDIS version.
]

disreb: make object! [
	host: tcp://localhost:6379
	s: none
	
	link: func [
		"Connect to redis server."
	] [s: open/binary/no-wait host]

	format: func [
		"Compose redis command string."
		parms 
		/local pad word
	] [
		pad: reduce ["*"  length? parms  crlf]
		forall parms [
			word: to-string parms/1
			append pad reduce [
				"$"  length? word  crlf
				word  crlf
			]
		]

		rejoin pad
	]

	munch: func [
		"Consume data from server up to crlf."
		/local pad
	] [
		pad: copy ""
		while [any [empty? pad  lf <> last pad]] [
			append pad to-char first s
		]
		copy/part pad (length? pad) - 2 ; Strip off crlf.
	]

	ask: func [
		"Send command to db and interpret result."
		command [block!] "REDIS command."
		/local d n nb
	] [
		; print ["Command:"  mold command] ; Debug.
		insert s format command
		wait s

		d: copy []  n: 0
		forever [
			if error? try [c: to-char first s] [d: reduce ['empty]  break]
			; print ["Result type:"  mold c] ; Debug.
			
			switch c [
				#"+" [d: reduce ['ok munch]  break]
				#"-" [d: reduce ['error munch]  break]
				#":" [d: reduce ['integer  to-integer munch]  break]
				#"*" [n: to-integer munch]
				#"$" [
					nb: to-integer munch
					if -1 = nb [d: reduce ['empty none]  break]

					append d to-string copy/part s nb
					munch ; Consume final crlf.
					if zero? n [d: reduce ['string d/1]  break]
					if n = length? d [d: reduce ['multi d]  break]
				]
			]
		]
		
		if 'error = first d [ ] ; XXX Should throw error.
		d
	]

	askf: func [
		"Send command to db, expecting a logic (flag) result."
		command [block!] "REDIS command."
		/local result
	] [
		result: ask compose command
		if 'integer <> first result [ ] ; XXX Throw error for bad result.
		to-logic last result
	]

	askl: func [
		"Send command to db, expecting to return last element of result."
		command [block!] "Composable REDIS command."
	] [last ask compose command]

; h2. REDIS commands

; h3. Keys

	del: func [
		"Remove specified keys. A key is ignored if it does not exist."
		keys [string! block!] "Key or keys to remove."
	] [askf [del (keys)]]

	exists: func [
		"Returns if key exists."
		key [string!]
	] [askf [exists (key)]]

	expire: func [
		"Set a timeout on key."
		key [string!]
		seconds [integer!]
	] [askf [expire (key) (seconds)]]

	expireat: func [
		"Set a timestamp timeout on key."
		key [string!]
		timestamp [integer!] "UNIX timestamp."
	] [askf [expireat (key) (seconds)]]

	keys: func [
		"Find all keys matching the given pattern."
		pattern [string!]
		/local result
	] [
		result: ask compose [keys (pattern)]
		if 'empty = result/1 [return copy []]
		result/2
	]

	move: func [
		"Move key from currently selected database to specified destination database."
		key [string!]
		db [integer!]
	] [askf [move (key) (db)]]

	persist: func [
		"Remove the existing timeout on key."
		key [string!]
	] [askf [persist (key)]]

	randomkey: func [
		"Return a random key from the currently selected database."
	] [ask [randomkey]]

	rename: func [ ; Interferes with REBOL command.
		"Renames key to newkey."
		key [string!]
		newkey [string!]
	] [askf [rename (key) (newkey)]]

	renamenx: func [
		"Renames key to newkey if newkey does not yet exist."
		key [string!]
		newkey [string!]
	] [askf [renamenx (key) (newkey)]]

; Skipping /sort/ for now. josh 2011-03-03

	ttl: func [
		"Returns the remaining time to live of a key that has a timeout."
		key [string!]
	] [askl [ttl (key)]]

	type: func [
		"Returns the string representation of the type of the value stored at key."
		key [string!]
	] [askl [type (key)]]

; h3. Strings

	_append: func [ ; Interferes with REBOL command.
		"If key already exists and is a string, this command appends the value at the end of the string."
		key [string!]
		value [string!]
	] [askl [append (key) (value)]]

	decr: func [
		"Decrements the number stored at key by one."
		key [string!]
	] [askl [decr (key)]]

	decrby: func [
		"Decrements the number stored at key by decrement."
		key [string!]
		decrement [integer!]
	] [askl [decrby (key) (decrement)]]

	_get: func [ ; Interferes with REBOL command.
		"Get the value of key."
		key [string!]
	] [askl [get (key)]]

	getbit: func [
		"Returns the bit value at offset in the string value stored at key."
		key [string!]
		offset [integer!]
	] [askl [getbit (key) (offset)]]

	getrange: func [
		"Returns the substring of the string value stored at key, determined by the offsets start and end."
		key [string!]
		start [integer!]
		end [integer!]
	] [askl [getrange (key) (start) (end)]]

	getset: func [
		"Atomically sets key to value and returns the old value stored at key."
		key [string!]
		value [string! integer!]
	] [askl [getset (key) (value)]]

	incr: func [
		"Increments the number stored at key by one."
		key [string!]
	] [askl [incr (key)]]

	incrby: func [
		"Increments the number stored at key by increment."
		key [string!]
		increment [integer!]
	] [askl [incr (key) (increment)]]

	mget: func [
		"Returns the values of all specified keys."
		keys [string! block!]
	] [askl [mget (keys)]]

	mset: func [
		"Sets the given keys to their respective values."
		sets [block!]
	] [
		; XXX Throw if length of block is not even.
		askf [mset (sets)]
	]

	msetnx: func [
		"Sets the given keys to their respective values."
		sets [block!]
	] [
		; XXX Throw if length of block is not even.
		askf [msetnx (sets)]
	]

	_set: func [ ; Interferes with REBOL command.
		"Set key to hold the string value."
		key [string!]
		value [integer! string!]
	] [askf [set (key) (value)]]

	setbit: func [
		"Sets or clears the bit at offset in the string value stored at key."
		key [string!]
		offset [integer!]
		value [logic!]
	] [askl [setbit (key) (offset) (value)]]

	setex: func [
		"Set key to hold the string value and set key to timeout after a given number of seconds."
		key [string!]
		seconds [integer!]
		value [integer! string!]
	] [askl [setex (key) (seconds) (value)]]

	setnx: func [
		"Set key to hold string value if key does not exist."
		key [string!]
		value [integer! string!]
	] [askf [setnx (key) (value)]]

	setrange: func [
		"Overwrites part of the string stored at key, starting at the specified offset, for the entire length of value."
		key [string!]
		offset [integer!]
		value [string!]
	] [askl [setrange (key) (offset) (value)]]

	strlen: func [
		"Returns the length of the string value stored at key."
		key [string!]
	] [askl [strlen (key)]]

; h3. Hashes

	hdel: func [
		"Removes field from the hash stored at key."
		key [string!]
		field [string!]
	] [askf [hdel (key) (field)]]

	hexists: func [
		"Returns if field is an existing field in the hash stored at key."
		key [string!]
		field [string!]
	] [askf [hexists (key) (field)]]

	hget: func [
		"Returns the value associated with field in the hash stored at key."
		key [string!]
		field [string!]
	] [askl [hget (key) (field)]]

	hgetall: func [
		"Returns all fields and values of the hash stored at key."
		key [string!]
	] [askl [hgetall (key)]]

	hincrby: func [
		"Increments the number stored at field in the hash stored at key by increment."
		key [string!]
		field [string!]
		increment [integer!]
	] [askl [hincrby (key) (field) (increment)]]

	hincr: func [
		"Increments the number stored at field in the hash."
		key [string!]
		field [string!]
	] [hincrby key field 1]

	hkeys: func [
		"Returns all field names of the hash stored at key."
		key [string!]
	] [askl [hkeys (key)]]

	hlen: func [
		"Returns the number of fields contained in the hash stored at key."
		key [string!]
	] [askl [hlen (key)]]

	hmget: func [
		"Returns the values associated with the specified fields in the hash stored at key."
		key [string!]
		fields [string! block!]
	] [askl [hmget (key) (fields)]]

	hmset: func [
		"Sets the specified fields to their respective values in the hash stored at key."
		key [string!]
		sets [block!]
	] [
		; XXX Throw if length of block is not even.
		askf [hmset (key) (sets)]
	]

	hset: func [
		"Sets field in the hash stored at key to value."
		key [string!]
		field [string!]
		value [integer! string!]
	] [askf [hset (key) (field) (value)]]

	hsetnx: func [
		"Sets field in the hash stored at key to value, only if field does not yet exist."
		key [string!]
		field [string!]
		value [integer! string!]
	] [askf [hsetnx (key) (field) (value)]]

	hvals: func [
		"Returns all values of the hash stored at key."
		key [string!]
	] [askl [hvals (key)]]

; h3. Lists

; h3. Sets

	sadd: func [
		"Add member to the set stored at key."
		key [string!]
		member [string!]
	] [askf [sadd (key) (member)]]

	scard: func [
		"Returns the set cardinality (number of elements) of the set stored at key."
		key [string!]
	] [askl [scard (key)]]

	sdiff: func [
		"Returns difference between the first set and all the successive sets."
		key [string!]
		keys [string! block!]
	] [askl [sdiff (key) (keys)]]

	sdiffstore: func [
		"This command is equal to SDIFF, but instead of returning the resulting set, it is stored in destination."
		destination [string!]
		key [string!]
		keys [string! block!]
	] [askl [sdiffstore (destination) (key) (keys)]]

	sinter: func [
		"Returns the members of the set resulting from the intersection of all the given sets."
		keys [string! block!]
	] [askl [sinter (keys)]]

	sinterstore: func [
		"This command is equal to SINTER, but instead of returning the resulting set, it is stored in destination."
		destination [string!]
		keys [string! block!]
	] [askl [sinterstore (destination) (keys)]]

	sismember: func [
		"Returns if member is a member of the set stored at key."
		key [string!]
		member [string!]
	] [askf [sismember (key) (member)]]

	smembers: func [
		"Returns all the members of the set value stored at key."
		key [string!]
	] [askl [smembers (key)]]

	smove: func [
		"Move member from the set at source to the set at destination."
		source [string!]
		destination [string!] 
		member [string!]
	] [askf [smove (source) (destination) (member)]]
		
	spop: func [
		"Removes and returns a random element from the set value stored at key."
		key [string!]
	] [askl [spop (key)]]

	srandmember: func [
		"Returns a random element from the set value stored at key."
		key [string!]
	] [askl [srandmember (key)]]

	srem: func [
		"Remove member from the set stored at key."
		key [string!]
		member [string!]
	] [askf [srem (key) (member)]]

	sunion: func [
		"Returns the members of the set resulting from the union of all the given sets."
		keys [string! block!]
	] [askl [sunion (keys)]]

	sunionstore: func [
		"This command is equal to SUNION, but instead of returning the resulting set, it is stored in destination."
		destination [string!]
		keys [string! block!]
	] [askl [sunionstore (destination) (keys)]]

; h3. Sorted Sets

; h3. Pub/Sub

; h3. Transaction

; h3. Connection

	auth: func [
		"Authenticate to the server."
		password [string!]
	] [ask compose [auth (password)]]

	echo: func [ ; Interferes with REBOL command.
		"Returns message."
		message [string!]
	] [ask compose [echo (message)]]

	ping: func [
		"Returns PONG."
	] [ask [ping]]

	quit: func [ ; Interferes with REBOL command.
		"Ask the server to close the connection."
	] [ask [quit]]

	select: func [ ; Interferes with REBOL command.
		"Select the DB with having the specified zero-based numeric index."
		db [integer!]
	] [ask compose [select (db)]]

; h3. Server

]

test: [
	r: make disreb []
	r/link

	print mold r/ping

	r/select 0
	print mold r/keys "*"

	print mold r/exists "name"
	print mold r/exists "gark"

	print mold r/mget ["name" "test"]

	; r/askf [select 2]
	r/select 2
	print mold r/randomkey
]